﻿//-----------------------------------------------------------------------
// <copyright file="FilterWrapper.cs" company="Microsoft Corporation">
// Copyright (c) Microsoft Corporation.
// All rights reserved.
// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY 
// KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
// PARTICULAR PURPOSE.
// This code released under the terms of the 
// Microsoft Public License (MS-PL, http://opensource.org/licenses/ms-pl.html.)
// </copyright>
// <author>Yogesh M Joshi</author>
// <email>ymjoshi@live.com</email>
// <date>16-May-2011</date>
//-----------------------------------------------------------------------

namespace SmartTag.Common.IFilter
{
    using System;
    using System.IO;
    using System.Runtime.InteropServices;
    using SmartTag.Common.Utility;

    /// <summary>
    /// Represents IFilter com component
    /// Its a wrapper over Filter
    /// </summary>
    public class FilterWrapper : IDisposable
    {
        #region "Private Fields"

        /// <summary>
        /// IFilter COM interface id
        /// </summary>
        private const string IFilterInterfaceId = "{89BCB740-6119-101A-BCB7-00DD010655AF}";

        /// <summary>
        /// Filter COM object
        /// </summary>
        private IFilter filterObject;

        /// <summary>
        /// Extension of the file
        /// </summary>
        private string extension;

        #endregion

        /// <summary>
        /// Initializes a new instance of the FilterWrapper class
        /// </summary>
        /// <param name="extension">Extenstion of file</param>
        public FilterWrapper(string extension)
        {
            this.extension = extension;
        }

        /// <summary>
        /// Dispose this object
        /// </summary>
        public void Dispose()
        {
            if (this.filterObject != null)
            {
                Marshal.ReleaseComObject(this.filterObject);
            }
        }

        /// <summary>
        /// Initialize the filter object from given stream
        /// </summary>
        /// <param name="stream">Stream of file content</param>
        public void InitializeFromStream(Stream stream)
        {
            // Load the filter object
            this.LoadFilter();

            if (this.filterObject == null)
            {
                return;
            }

            IPersistStream persistStream = this.filterObject as IPersistStream;

            if (stream != null)
            {
                try
                {
                    // Load the stream
                    persistStream.Load(new StreamWrapper(stream));
                    IFILTER_FLAGS flags;
                    IFILTER_INIT iflags =
                                IFILTER_INIT.CANON_HYPHENS |
                                IFILTER_INIT.CANON_PARAGRAPHS |
                                IFILTER_INIT.CANON_SPACES |
                                IFILTER_INIT.APPLY_INDEX_ATTRIBUTES |
                                IFILTER_INIT.HARD_LINE_BREAKS |
                                IFILTER_INIT.FILTER_OWNED_VALUE_OK;

                    // Check for the successul initialization of the filter object
                    if (this.filterObject.Init(iflags, 0, IntPtr.Zero, out flags) != IFilterReturnCode.S_OK)
                    {
                        Marshal.ReleaseComObject(this.filterObject);
                        this.filterObject = null;
                    }
                }
                catch (Exception ex)
                {
                    // Error while initializing filter
                    Marshal.ReleaseComObject(this.filterObject);
                    this.filterObject = null;
                    Logger.LogError(ex, "Error occured in InitializeFromStream", (int)TraceLogEventId.InitializeFromStream);
                }
            }
        }

        #region "Filter Interface Wrapper"

        /// <summary>
        /// Get the new chunk
        /// </summary>
        /// <param name="status">Chunk status</param>
        /// <returns>Filter return code</returns>
        public IFilterReturnCode GetChunk(out STAT_CHUNK status)
        {
            if (this.filterObject == null)
            {
                status = new STAT_CHUNK();
                return IFilterReturnCode.FILTER_E_END_OF_CHUNKS;
            }

            return this.filterObject.GetChunk(out status);
        }

        /// <summary>
        /// Get text in buffer
        /// </summary>
        /// <param name="count">Buffer length</param>
        /// <param name="buffer">Buffer containing characters</param>
        /// <returns>Filter status</returns>
        public IFilterReturnCode GetText(ref uint count, char[] buffer)
        {
            if (this.filterObject == null)
            {
                count = 0;
                return IFilterReturnCode.FILTER_E_END_OF_CHUNKS;
            }

            return this.filterObject.GetText(ref count, buffer);
        }

        /// <summary>
        /// Get the value
        /// </summary>
        /// <param name="value">Return value</param>
        /// <returns>Return code</returns>
        public int GetValue(ref IntPtr value)
        {
            return this.filterObject.GetValue(ref value);
        }

        #endregion

        #region "Private Helper Methods"
        /// <summary>
        /// Loads the filter object for specified extenstion
        /// </summary>
        private void LoadFilter()
        {
            try
            {
                var filterInfo = FilterInfoCache.GetEntry(this.extension);
                string key = string.Empty;
                string persistentHandlerId = string.Empty;
                string filterPHGuid = string.Empty;
                string dllName = string.Empty;

                // Now we have to load information from registry
                if (filterInfo == null)
                {
                    // Find by extension
                    persistentHandlerId = this.GetPHIdFromExtenstion();

                    if (string.IsNullOrEmpty(persistentHandlerId))
                    {
                        persistentHandlerId = this.GetPHIdFromDocType();
                    }

                    // We found the persistent handler GUID now find the IFilter Interface GUID and DLL name
                    if (string.IsNullOrEmpty(persistentHandlerId) == false)
                    {
                        filterPHGuid = this.GetIFilterPHId(persistentHandlerId);
                        dllName = this.GetDllName(filterPHGuid);

                        // Add entry to cache
                        filterInfo = FilterInfoCache.AddEntry(this.extension, dllName, filterPHGuid);
                    }
                }

                // Check if we are able to locate the filter
                if (filterInfo != null)
                {
                    // Load the IFilter dll
                    this.filterObject = this.LoadFilterComponent(filterInfo.DllName, filterInfo.IPersistClass);
                }
            }
            catch (Exception ex)
            {
                Logger.LogError(ex, "Error occured in LoadFilter", (int)TraceLogEventId.LoadFilter);
            }
        }

        /// <summary>
        /// Gets the registered perisistent handler ID from extension
        /// </summary>
        /// <returns>Persistent handler ID</returns>
        private string GetPHIdFromExtenstion()
        {
            string key = string.Format(@"SOFTWARE\Classes\{0}\PersistentHandler", this.extension);
            string persistentHandlerId = RegistryHelper.ReadStringValueFromLocalMachine(key, string.Empty);
            return persistentHandlerId;
        }

        /// <summary>
        /// Gets persistent Handler ID from document type
        /// </summary>
        /// <returns>Persistent Handler ID</returns>
        private string GetPHIdFromDocType()
        {
            try
            {
                string key = string.Format(@"SOFTWARE\Classes\{0}", this.extension);
                string documentType = RegistryHelper.ReadStringValueFromLocalMachine(key, string.Empty);

                if (string.IsNullOrEmpty(documentType))
                {
                    return string.Empty;
                }

                key = string.Format(@"SOFTWARE\Classes\{0}\CLSID", documentType);
                string clsId = RegistryHelper.ReadStringValueFromLocalMachine(key, string.Empty);

                // Unable to locate CLSID
                if (string.IsNullOrEmpty(clsId))
                {
                    return string.Empty;
                }

                // Got the clsid now try getting persistent handler id
                key = string.Format(@"SOFTWARE\Classes\CLSID\{0}\PersistentHandler", clsId);
                string persistentHandlerId = RegistryHelper.ReadStringValueFromLocalMachine(key, string.Empty);
                return persistentHandlerId;
            }
            catch (Exception ex)
            {
                Logger.LogError(ex, "Error occured in GetPHIdFromDocType", (int)TraceLogEventId.GetPHIdFromDocType);
                return null;
            }
        }

        /// <summary>
        /// Gets the registered IFilter Persistent Handler Id
        /// </summary>
        /// <param name="persistentHandlerId">Persistent Handler ID</param>
        /// <returns>IFilter Persistent Handler ID</returns>
        private string GetIFilterPHId(string persistentHandlerId)
        {
            string key = string.Format(@"SOFTWARE\Classes\CLSID\{0}\PersistentAddinsRegistered\{1}", persistentHandlerId, IFilterInterfaceId);
            string ifilterPHId = RegistryHelper.ReadStringValueFromLocalMachine(key, string.Empty);
            return ifilterPHId;
        }

        /// <summary>
        /// Get the filter dll name
        /// </summary>
        /// <param name="filterPersistentHandlerId">IFilter Persistent Handler ID</param>
        /// <returns>Name of the IFilter DLL</returns>
        private string GetDllName(string filterPersistentHandlerId)
        {
            // We got GUID now need to get DLL name
            string key = string.Format(@"SOFTWARE\Classes\CLSID\{0}\InprocServer32", filterPersistentHandlerId);
            string dllName = RegistryHelper.ReadStringValueFromLocalMachine(key, string.Empty);
            return dllName;
        }

        /// <summary>
        /// Loads the filter dll
        /// </summary>
        /// <param name="dllName">Name of the dll to load</param>
        /// <param name="filterPersistClass">IFilter Persistent class ID</param>
        /// <returns>IFilter COM object</returns>
        private IFilter LoadFilterComponent(string dllName, string filterPersistClass)
        {
            try
            {
                // Get a classFactory for given class id
                IClassFactory classFactory = ComHelper.GetClassFactory(dllName, filterPersistClass);

                if (classFactory == null)
                {
                    return null;
                }

                // Create an IFilter instance using that class factory
                Guid filterGUID = new Guid(IFilterInterfaceId);
                object obj;
                classFactory.CreateInstance(null, ref filterGUID, out obj);
                return obj as IFilter;
            }
            catch (Exception ex)
            {
                Logger.LogError(ex, "Error occured in LoadFilterComponent", (int)TraceLogEventId.LoadFilterComponent);
                return null;
            }
        }

        #endregion
    }
}
